
<!DOCTYPE html>
<html lang="">


<head><meta name="generator" content="Hexo 3.8.0">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
  <meta name="theme-color" content="#202020">
  <meta http-equiv="x-ua-compatible" content="ie=edge">
  <script src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js" async></script>
  
  
    <meta name="keywords" content>
  

  
    <meta name="description" content="DevOps 和 SRE 的定义和异同">
  
  
  
  <link rel="icon" type="image/x-icon" href="/images/footer-logo.png">
  
  <title>DevOps 和 SRE 的定义和异同 [ 51AIOps 专注于运维自动化  微信： kaipython ]</title>
  
    <!-- stylesheets list from config.yml -->
    
      <link rel="stylesheet" href="//cdn.bootcss.com/pure/1.0.0/pure-min.css">
    
      <link rel="stylesheet" href="/css/xoxo.css">
    
  
</head>


<body>
  <div class="nav-container">
    <nav class="home-menu pure-menu pure-menu-horizontal">
  <a class="pure-menu-heading" href="/">
    
    <span class="title" style="text-transform:none">51AIOps 专注于运维自动化  微信： kaipython</span>
  </a>

  <ul class="pure-menu-list clearfix">
      
          
            
              <li class="pure-menu-item"><a href="/" class="pure-menu-link">首页</a></li>
            
          
      
  </ul>
   
</nav>

  </div>

  <div class="container" id="content-outer">
    <div class="inner" id="content-inner" style='margin-left:-68px!important'>
      <div class="post-container">
  <article class="post" id="post">
    <header class="post-header text-center">
      <h1 class="title">
        DevOps 和 SRE 的定义和异同
      </h1>
      <span>
        
        <time class="time" datetime="2019-10-07T16:00:00.000Z">
        2019-10-08
      </time>
        
      </span>
      <span class="slash">/</span>
      <span class="post-meta">
      <span class="post-tags">
        
      </span>
    </span>
      <span class="slash">/</span>
      <span class="read">
      <span id="busuanzi_value_page_pv"></span> 点击
    </span>
      <span class="slash">/</span>
    </header>

    <div class="post-content">
      <p>注释：这篇文章是转载公司内部的一篇文章。</p>
<p>DevOps 和 SRE，尤其是 DevOps，是最近几年软件研发方法的重要趋势。因为它们都跟打通开发和运维流程相关，所以常常被混淆。比如，SRE 等同于 Google 的 DevOps、SRE 是升级版的 DevOps，就是两个常见的误区。</p>
<p>事实上，DevOps 和 SRE 虽然关系紧密，但差别还是蛮大的。所以今天，我首先会通过 DevOps 和 SRE 的对比，引出它们背后的 Why、How 和 What（也就是它们的目标、原则和具体实践）。然后，我会结合自己在一个创业公司的经验，向你推荐如何在团队落地 DevOps。</p>
<h2 id="DevOps-和-SRE-的定义和异同"><a href="#DevOps-和-SRE-的定义和异同" class="headerlink" title="DevOps 和 SRE 的定义和异同"></a>DevOps 和 SRE 的定义和异同</h2><p>因为 DevOps 和 SRE 都是比较新的概念，而且在不断地发展变化，所以学术界和工业界对它们的定义并未达成一致。</p>
<p>接下来，我参考已有定义，并加入自己的理解，对 DevOps 和 SRE 的大致定义如下：</p>
<p><strong>DevOps</strong>，Development 和 Operations 的组合词，是一种重视“软件开发人员（Dev）”和“IT 运维技术人员（Ops）”之间沟通合作的文化、活动或惯例。 通过自动化“软件交付”和“架构变更”的流程，使得软件的构建、测试、发布更加快捷、频繁和可靠。</p>
<p><strong>SRE</strong>，全称是 Site Reliability Engineer ，网站可靠性工程师，是一个职位，是软件工程师和系统管理员的结合，主要目标是创建可扩展且高度可靠的软件系统。</p>
<p>为达到这个目标，SRE 需要掌握如下相关知识：算法、数据结构、编程、网络编程、分布式系统、可扩展架构、故障排除等。SRE 使用工具和系统支撑其完成工作，比如自动化发布系统、监控系统、日志系统、服务器资源分配和编排工具等，而这些工具往往需要他们自己开发和维护。</p>
<p>所以总结来说，<strong>DevOps 是打通开发和运维的文化和惯例，而 SRE 是 DevOps 的具体实践之一</strong>。说到相同点，它们都是为了打通 Dev 和 Ops，提高研发效能；说到区别，DevOps 是文化，而 SRE 是职位。如果要类比的话，<strong>DevOps 与 SRE 的关系，就像敏捷跟 Scrum 的关系。</strong></p>
<p>理解了 DevOps 和 SRE 的定义和异同后，我们再来看看它们的目标、原则和具体实践吧。</p>
<h2 id="DevOps-和-SRE-的-Why、How、What"><a href="#DevOps-和-SRE-的-Why、How、What" class="headerlink" title="DevOps 和 SRE 的 Why、How、What"></a>DevOps 和 SRE 的 Why、How、What</h2><p>不知道你有没有考虑过，传统定义中，开发人员和运维人员的利益其实是冲突的。</p>
<p>开发人员的职责是开发功能。功能上线越多，对团队和公司的贡献就越大。所以，开发人员倾向于多开发功能，并快速上线。而运维人员的主要职责是，上线功能，以及保证系统的稳定性，更关注系统的稳定性。新功能上线越多，工作量就越大，服务越容易不稳定，所以运维人员不愿意功能多上线、快速上线。</p>
<p>另外，职能竖井越严重，这些问题就越严重。因为在这种情况下，开发人员写完代码就扔给运维人员撒手不管了，运维人员也很少能从开发人员手里拿到有效信息。此时，线上问题的修复对运维人员来说挑战很大。于是，他们会更加谨慎。</p>
<p><strong>这就是 DevOps 和 SRE 最初要解决的问题：开发和运维这两个角色的目标不一致，导致研发和上线流程的不顺畅，最终严重影响软件上线的效率。</strong>比如，运维团队倾向设置多而严格的上线检查门禁，限制上线频率。而开发人员则很可能把一些功能“伪装”成 Bug 修复，来绕过针对版本发布的严格检查。</p>
<p>那怎么解决这个问题呢？接下来，我向你推荐以下几个原则和具体方法。</p>
<h3 id="原则-1：协调运维和开发人员的目标、利益"><a href="#原则-1：协调运维和开发人员的目标、利益" class="headerlink" title="原则 1：协调运维和开发人员的目标、利益"></a>原则 1：协调运维和开发人员的目标、利益</h3><p>目标、利益不一致，是导致开发团队和运维团队矛盾的首要问题，所以想办法让它们的目标、利益变得一致，是整个 DevOps 中最重要的一条。因为只有协调好了目标和利益，它们才有动力去解决问题。实现这个原则的一个最主要的方法，我称之为“<strong>全栈开发</strong>”。</p>
<p>什么意思呢？全栈开发，就是每一个工程人员的工作涵盖了不止一个领域。虽然他专攻某一个领域，但对负责的领域都有责任。说白了，就是对产品结果负责，而不是只对某一个具体环节负责。</p>
<p>具体的实施方法，主要包括两大方面。</p>
<p><strong>第一，增加一个新的运维角色，用开发的方式去做运维。</strong>不同于传统运维人员主要关注网站和服务的稳定和快速，这种新型运维角色负责帮助开发人员推动业务快速开发、快速上线，具体工作包括：优化流程，提供自助工具。</p>
<p>在 Facebook，这个角色由一个专门的发布工程师团队承担。而在 Google，这个角色就是 SRE。具体来讲，SRE 的工作职责是负责日常运维、紧急响应、工具研发、建设平台化服务体系、容量规划和容量管理、Oncall 等。他们会被指派到具体的产品团队中，深入到开发第一线，拿出至少 50% 的时间进行编程工作，针对性地自动化和优化 CI/CD 中的流程、工具等。这里请注意，他们的开发工作不是业务开发，而是工具开发和自动化等。</p>
<p>与 Facebook 的发布工程师团队相比，SRE 的最大特点是更多地参与到具体产品和项目中。为了方便讨论，<strong>我把 SRE 和发布工程师团队这种新的运维角色，统称为“类 SRE”。</strong>类 SRE 顺利推进的关键是，找到高质量的开发、运维多面手，也就是有很强的开发能力，同时有系统维护、网络问题排查等运维技能的工程师。</p>
<p>需要注意的是，除了类 SRE，传统的运维角色（比如网络工程师、系统工程师）仍然存在，他们也使用类 SRE 提供的 DevOps 工具链提高工作效率，所以人员数量比以前少了很多。在遇到线上问题时，开发、类 SRE 和传统运维需要配合解决。</p>
<p><strong>第二，修改开发人员的职责描述为，快速开发和上线稳定的高质量产品，让他们也参与到一部分运维工作中去。</strong></p>
<p>开发人员最主要的工作仍然是开发，但会使用类 SRE 团队提供的工具、流程来进行发布相关的工作，包括代码部署、线上问题定位和处理等部分工作。这样，他们会在代码开发时就注意提高服务的稳定性和代码质量。</p>
<p>比如在 Facebook 进行日部署的时候，开发人员写好了修复的代码提交，还需要去找到提交所依赖的、还没有上线的其他提交，跟这些提交的作者进行确认，没有问题后才可以一起上线。</p>
<p>上线时，这个开发人员对这一组提交负责，把它们全部提交到日部署流程工具上，并负责功能验证。而负责部署的发布工程师，则会使用工具自动化地把这些提交部署到日部署环境中，并进行系统验证。可以看到，开发人员较多地参与了整个部署过程，这正是持续开发的体现。</p>
<p>另一个比较直观地体现开发人员参与运维工作的实践是，让开发人员 Oncall，也就是身上别个 BP 机，线上出现问题要马上响应，即使半夜三更也要爬起来，让功能的开发人员承担起解决一线问题的职责。</p>
<p>通过引入类 SRE 角色和修改开发者职责描述这两种方法，开发人员和部分运维人员的职责，都从原来的单一职能扩展到了产品的高效开发上线，从根本目标和利益上实现了这两个角色的对齐。</p>
<p>所以说，“全栈开发”方式非常重要，效果也非常好。这，也正是我把“全栈”用在文章标题中的原因。</p>
<h3 id="原则-2：推动高效沟通"><a href="#原则-2：推动高效沟通" class="headerlink" title="原则 2：推动高效沟通"></a>原则 2：推动高效沟通</h3><p>之所以强调推动高效沟通，就是为了解决开发和运维因为存在“部门墙”而导致的信息不流通的问题。</p>
<p>具体的实施方法包括：</p>
<ul>
<li>设置聊天室（比如 IRC），用于沟通部署进展和问题。</li>
<li>引入 ChatOps，也就是通过自动化的方式，提高部署相关的沟通。比如，可以让聊天机器人在 ChatOps 聊天室中自动发送发布流程的进展，也可以向聊天机器人询问服务部署进展等。这些自动化可以节省很多部署人员的时间。</li>
<li>把任务、代码提交、发布的关联关系，在工具中呈现出来，并提供比如任务看板、系统监控看板等可视化工具，显示开发、运维重点信息。这样可以大大提高运维人员定位问题的效率。</li>
</ul>
<h3 id="原则-3：优化从开发到部署的整个上线流程"><a href="#原则-3：优化从开发到部署的整个上线流程" class="headerlink" title="原则 3：优化从开发到部署的整个上线流程"></a>原则 3：优化从开发到部署的整个上线流程</h3><p>优化上线流程，主要目的是解决频繁上线工作量大、产品质量不稳定的问题。</p>
<p>这个原则的主要方法是优化代码入库和部署上线流程，并最大化地利用工具来自动化流程。最近 10 年这个原则发展得非常快，逐渐涵盖了快速、高质量上线的各方面工作。这些方面的效率也被称作<strong>交付效能，</strong>具体实践包括：基于主干开发、持续集成、持续交付、持续部署、实现系统松耦合等。</p>
<p>前 4 条实践，我们已经在前面的文章中讲解过了；而关于系统松耦合，我会在“工程方法”模块中与你详细讨论。</p>
<p>理解了 DevOps 和 SRE 的目标、方法和具体实践后，我们再来看看具体怎么落地吧。</p>
<h2 id="落地步骤推荐"><a href="#落地步骤推荐" class="headerlink" title="落地步骤推荐"></a>落地步骤推荐</h2><p>在落地 DevOps 时，我经常看到有些团队一上来就去寻找工具，比如使用 Jenkins 来搭建流程。但正如上面所说，DevOps 的本质是解决开发和运维之间的冲突，所以落地时首先要从人出发，然后是流程，最后才是工具。</p>
<p>所以，我推荐的具体落地步骤是：</p>
<ol>
<li>对团队目标达成共识，并重新定义职责；</li>
<li>设计 CI、CD、快速反馈，以及团队沟通等流程；</li>
<li>引入工具，实现自动化。</li>
</ol>
<p>接下来，我就用我在一家创业公司的实践来帮助你理解这些落地步骤。</p>
<h2 id="落地实践案例"><a href="#落地实践案例" class="headerlink" title="落地实践案例"></a>落地实践案例</h2><p>这个案例，是我在一个创业公司落地 DevOps 的实例。当时公司刚成立不久，我的角色是技术总负责人，操作的自由度很大。为了提高产品研发的交付效能，也就是团队快速发布高质量软件的能力，我采用了以下步骤和方法。</p>
<h3 id="第一，在团队内部统一认识"><a href="#第一，在团队内部统一认识" class="headerlink" title="第一，在团队内部统一认识"></a>第一，在团队内部统一认识</h3><p>研发团队内部统一认识，主要是让团队成员明确我们的目标一致。同时在做绩效考评时，对大家的要求都是产品快速、高质量上线。</p>
<p>我还把“统一认识”这一点，扩展到研发以外的部分，比如市场、设计团队。在推广时，我始终注意以公司利益为出发点，而不是按照职能划分来追责，获得了 CEO 和其他团队管理者的支持。最终，推广得比较顺利。</p>
<h3 id="第二，增加“发布工程师”角色"><a href="#第二，增加“发布工程师”角色" class="headerlink" title="第二，增加“发布工程师”角色"></a>第二，增加“发布工程师”角色</h3><p>这个角色的职责，相当于 Facebook 发布工程师团队的职责。因为我在部署、运维方面的经验相对丰富，便担起了这个工作，投入相当一部分精力去设计、优化产品的开发和上线流程，包括持续集成、持续交付、手动部署流程，以及问题监控流程和 Oncall 机制。</p>
<p>其他开发人员则使用这个流程来部署、上线、监控、解决问题，提高交付效能。在这个过程中，我作为发布工程师，主要起到了“使能”的作用，即日常的部署上线由开发人员自己负责，而我只参与解决一部分运维相关的难题。</p>
<h3 id="第三，设计问题沟通流程"><a href="#第三，设计问题沟通流程" class="headerlink" title="第三，设计问题沟通流程"></a>第三，设计问题沟通流程</h3><p>在解决了“人”的问题之后，我在流程方面，设计了部署沟通流程，以及部署系统常见问题及解决办法收集流程。</p>
<p>部署沟通流程是：建立了专门的聊天室沟通持续集成、持续交付，以及手动部署的进展情况，确保发布上线流程相关信息的顺畅流通。</p>
<p>部署系统常见问题及解决办法收集流程是：在解决完线上问题之后，记录下问题的细节和解决步骤。很简单，就是因为很多问题会重复出现。每个人都可以更改、搜索这些记录，对后续解决问题帮助非常大。所以，我们每次遇到问题，都会先搜索这个知识库，找到答案和线索的概率能达到百分之七八十。</p>
<h3 id="第四，用工具来实现流程自动化"><a href="#第四，用工具来实现流程自动化" class="headerlink" title="第四，用工具来实现流程自动化"></a>第四，用工具来实现流程自动化</h3><p>完成了有关人和流程的工作之后，工具的实现就不那么难了。DevOps 相关工具主要包括两大类：CI/CD 工具链和沟通工具。</p>
<p>通过这一系列针对人、流程、工具的措施，整个研发团队实现了全栈开发，目标一致、流程顺畅，能够聚焦于开发，交付效能非常高，实现了后端服务每周上线三次，热修复上线时间 5 分钟，MTTR（Mean Time To Recovery）大概 15 分钟，线上也很少出问题，系统的可用性达到 4 个 9（即 99.99%）。</p>
<p>同时，对于开发者来说，全栈开发机制也优化了开发体验，提升了技术成长速度。</p>
<p>当然，这些实践能够顺利推广，与这是一个小的初创公司有关。一方面，公司存亡直接关乎个人利益；另一方面，一切都在建设期，引入具体实践的阻力非常小。如果是在大的团队，或者是职能已经成型的团队，实施的困难会相对较大。这时，我建议你从以下 3 个方面入手：</p>
<ul>
<li>在团队普及全栈开发的理念；</li>
<li>在你管理范围内推行全栈开发，做出效果；</li>
<li>通过实际运行效果获得公司管理层的支持，让他们了解全栈开发模式为提升研发效能带来的好处，从而逐步改变职责定义和流程。</li>
</ul>

    </div>

  </article>
  <div class="toc-container">
    
  <div id="toc" class="toc-article">
    <strong class="toc-title">目录</strong>
    <ol class="toc"><li class="toc-item toc-level-2"><a class="toc-link" href="#DevOps-和-SRE-的定义和异同"><span class="toc-text">DevOps 和 SRE 的定义和异同</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#DevOps-和-SRE-的-Why、How、What"><span class="toc-text">DevOps 和 SRE 的 Why、How、What</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#原则-1：协调运维和开发人员的目标、利益"><span class="toc-text">原则 1：协调运维和开发人员的目标、利益</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#原则-2：推动高效沟通"><span class="toc-text">原则 2：推动高效沟通</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#原则-3：优化从开发到部署的整个上线流程"><span class="toc-text">原则 3：优化从开发到部署的整个上线流程</span></a></li></ol></li><li class="toc-item toc-level-2"><a class="toc-link" href="#落地步骤推荐"><span class="toc-text">落地步骤推荐</span></a></li><li class="toc-item toc-level-2"><a class="toc-link" href="#落地实践案例"><span class="toc-text">落地实践案例</span></a><ol class="toc-child"><li class="toc-item toc-level-3"><a class="toc-link" href="#第一，在团队内部统一认识"><span class="toc-text">第一，在团队内部统一认识</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#第二，增加“发布工程师”角色"><span class="toc-text">第二，增加“发布工程师”角色</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#第三，设计问题沟通流程"><span class="toc-text">第三，设计问题沟通流程</span></a></li><li class="toc-item toc-level-3"><a class="toc-link" href="#第四，用工具来实现流程自动化"><span class="toc-text">第四，用工具来实现流程自动化</span></a></li></ol></li></ol>
  </div>


  </div>
</div>
<script type="text/javascript" src="//rf.revolvermaps.com/0/0/8.js?i=5sr5du46f27&amp;m=0&amp;c=ff0000&amp;cr1=ffffff&amp;f=arial&amp;l=33" async="async"></script>
<div class="copyright">
    <span>本作品采用</span>
    <a href="https://creativecommons.org/licenses/by/4.0/">知识共享署名 4.0 国际许可协议</a>
    <span>进行许可。 转载时请注明原文链接。</span>
</div>


  
    <div class="post-nav" style="margin-left:-168px;">
      <div class="post-nav-item post-nav-next">
        
          <span>〈 </span>
          <a href="/2019/09/29/DjangoOrm模型的使用/" rel="next" title="第二十一章 Django的模型">
          第二十一章 Django的模型
          </a>
        
      </div>
  
      <div class="post-nav-item post-nav-prev">
          
          <a href="/2019/10/09/Git合并多个提交/" rel="prev" title="Git合并多个提交">
            Git合并多个提交
          </a>
          <span>〉</span>
        
      </div>
    </div>
  


	
	<div style="width:109%; margin-left:-144px" id="lv-container" data-id="city" data-uid="MTAyMC80OTg5NS8yNjM4Ng==">
	<script type="text/javascript">
   	   (function(d, s) {
       		var j, e = d.getElementsByTagName(s)[0];

       		if (typeof LivereTower === 'function') { return; }

       		j = d.createElement(s);
       		j.src = 'https://cdn-city.livere.com/js/embed.dist.js';
       		j.async = true;

       		e.parentNode.insertBefore(j, e);
   	   })(document, 'script');
	</script>
	<noscript> 为正常使用来必力评论功能请激活JavaScript</noscript>
        </div>
	



    </div>

    

  </div>
  <footer class="footer text-center">
    <div id="bottom-inner">
        <a class="bottom-item" href target="_blank">GitHub</a> |
        <a class="bottom-item" href>友情链接</a> |
        <a class="bottom-item" href="https://hexo.io" target="_blank">Powered by hexo</a> |
        <a class="bottom-item" href="https://github.com/fooying/hexo-theme-xoxo-plus" target="_blank">Theme xoxo-plus</a> |
        <a class="bottom-item" href="/atom.xml">订阅</a>
    </div>
</footer>

  

<script>
  (function(window, document, undefined) {

    var timer = null;

    function returnTop() {
      cancelAnimationFrame(timer);
      timer = requestAnimationFrame(function fn() {
        var oTop = document.body.scrollTop || document.documentElement.scrollTop;
        if (oTop > 0) {
          document.body.scrollTop = document.documentElement.scrollTop = oTop - 50;
          timer = requestAnimationFrame(fn);
        } else {
          cancelAnimationFrame(timer);
        }
      });
    }

    var hearts = [];
    window.requestAnimationFrame = (function() {
      return window.requestAnimationFrame ||
        window.webkitRequestAnimationFrame ||
        window.mozRequestAnimationFrame ||
        window.oRequestAnimationFrame ||
        window.msRequestAnimationFrame ||
        function(callback) {
          setTimeout(callback, 1000 / 60);
        }
    })();
    init();

    function init() {
      css(".heart{z-index:9999;width: 10px;height: 10px;position: fixed;background: #f00;transform: rotate(45deg);-webkit-transform: rotate(45deg);-moz-transform: rotate(45deg);}.heart:after,.heart:before{content: '';width: inherit;height: inherit;background: inherit;border-radius: 50%;-webkit-border-radius: 50%;-moz-border-radius: 50%;position: absolute;}.heart:after{top: -5px;}.heart:before{left: -5px;}");
      attachEvent();
      gameloop();
      addMenuEvent();
    }

    function gameloop() {
      for (var i = 0; i < hearts.length; i++) {
        if (hearts[i].alpha <= 0) {
          document.body.removeChild(hearts[i].el);
          hearts.splice(i, 1);
          continue;
        }
        hearts[i].y--;
        hearts[i].scale += 0.004;
        hearts[i].alpha -= 0.013;
        hearts[i].el.style.cssText = "left:" + hearts[i].x + "px;top:" + hearts[i].y + "px;opacity:" + hearts[i].alpha + ";transform:scale(" + hearts[i].scale + "," + hearts[i].scale + ") rotate(45deg);background:" + hearts[i].color;
      }
      requestAnimationFrame(gameloop);
    }

    /**
     * 给logo设置点击事件
     * 
     * - 回到顶部
     * - 出现爱心
     */
    function attachEvent() {
      var old = typeof window.onclick === "function" && window.onclick;
      var logo = document.getElementById("logo");
      if (logo) {
        logo.onclick = function(event) {
          returnTop();
          old && old();
          createHeart(event);
        }
      }
      
    }

    function createHeart(event) {
      var d = document.createElement("div");
      d.className = "heart";
      hearts.push({
        el: d,
        x: event.clientX - 5,
        y: event.clientY - 5,
        scale: 1,
        alpha: 1,
        color: randomColor()
      });
      document.body.appendChild(d);
    }

    function css(css) {
      var style = document.createElement("style");
      style.type = "text/css";
      try {
        style.appendChild(document.createTextNode(css));
      } catch (ex) {
        style.styleSheet.cssText = css;
      }
      document.getElementsByTagName('head')[0].appendChild(style);
    }

    function randomColor() {
      // return "rgb(" + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + "," + (~~(Math.random() * 255)) + ")";
      return "#F44336";
    }

    function addMenuEvent() {
      var menu = document.getElementById('menu-main-post');
      if (menu) {
        var toc = document.getElementById('toc');
        if (toc) {
          menu.onclick = function() {
            if (toc) {
              if (toc.style.display == 'block') {
                toc.style.display = 'none';
              } else {
                toc.style.display = 'block';
              }
            }
          };
        } else {
          menu.style.display = 'none';
        }
      }
    }

  })(window, document);
</script>

  



  

</body>
</html>
